/* C library */
#include <_stdlib.h>
#include <cstdint.h>
#include <string.h>
/* Debugging routines*/
#include "dbgio.h"
/* Memory management */
#include "pmm.h"
#include "vmm_ptm.h"
#include "vmm.h"
#include "kmm.h"
/* Process management */
#include "process.h"
/* Hardware interface */
#include "idt.h"
#include "gdt.h"
#include "pit.h"
#include "pic.h"
#include "text_video.h"
/* File system */
#include "ifs.h"
#include "initrd.h"
#include "fs.h"
/* Builtin DRI drivers */
#include "devfs.h"
#include "kromdisk.h"
#include "pty.h"
#include "peloader.h"
/* Testing */
#include <ustdio.h>

//! format of a memory region
struct memory_region {

	uint32_t	startLo;
	uint32_t	startHi;
	uint32_t	sizeLo;
	uint32_t	sizeHi;
	uint32_t	type;
	uint32_t	acpi_3_0;
};

//! different memory regions (in memory_region.type)
char* strMemoryTypes[] = {
	{"Available"},			//memory_region.type==0
	{"Reserved"},			//memory_region.type==1
	{"ACPI Reclaim"},		//memory_region.type==2
	{"ACPI NVS Memory"}		//memory_region.type==3
};

struct multiboot_info {

	uint32_t	m_flags;
	uint32_t	m_memorySize;
	uint32_t	m_memorySizeH;
	uint32_t	m_bootDevice;
	uint32_t	m_cmdLine;
	uint32_t	m_modsCount;
	uint32_t	m_modsAddr;
	uint32_t	m_syms0;
	uint32_t	m_syms1;
	uint32_t	m_syms2;
	uint32_t	m_mmap_length;
	uint32_t	m_mmap_addr;
	uint32_t	m_drives_length;
	uint32_t	m_drives_addr;
	uint32_t	m_config_table;
	uint32_t	m_bootloader_name;
	uint32_t	m_apm_table;
	uint32_t	m_vbe_control_info;
	uint32_t	m_vbe_mode_info;
	uint16_t	m_vbe_mode;
	uint32_t	m_vbe_interface_addr;
	uint16_t	m_vbe_interface_len;
};

void procfile_main();

/** Kernel entry point 
 *	Thanks to the nice bootloader we are using this can be full C and no startup code is needed :D
 */
extern "C" void __cdecl main(multiboot_info* bootinfo){
	/* Fetch important ram info */
	uint32_t kernelSize=0;
	uint32_t memSize=bootinfo->m_memorySize + bootinfo->m_memorySizeH*64;
	kmm_heap kernel_heap;
	_asm mov	word ptr [kernelSize], dx

	/* Initialize video driver and print banner */
	init_video();
	tprintf("[0x%X] - _main() \n",(void *)main);
	dprintf("Project Atlantis Kernel V0.0.4_%s  RAM Size: %i\n",CFG_KERNEL_TREE,memSize/1024);
	tprintf("   Kernel size: %i\n",kernelSize*512);

	/* Set up PMM and GDT */
	pmm_init ((size_t)memSize, 0xc0000000 + kernelSize*512);
	gdt_setup_ringed();

	/* Set up interrupts */
	x86_idt_initialize();
	x86_pic_initialize();

	/* Parse memory regions */
	//TODO: Move to PMM
	memory_region*	region = (memory_region*)0x1000;
	for (int i=0; i<10; ++i) {
		if (region[i].type>4)
			break;
		if (i>0 && region[i].startLo==0)
			break;
		pmm_init_region (region[i].startLo, region[i].sizeLo);
	}
	pmm_deinit_region (0, 0x600000);

	/* Setup kernel heap */
	memset(&kernel_heap,0,sizeof(kmm_heap));
	kmm_setup_heap(&kernel_heap,(void *)(0xC0000000 + kernelSize*512 + pmm_get_size())
		,0x400000 - (kernelSize*512 + pmm_get_size()),
		(physical_addr)(0x100000 + kernelSize*512 + pmm_get_size()));

	/* Set up page manager */
	vmm_initialize();
	ptm_init((virtual_addr)kmalloc(PTM_MAX_PTS * 1025));

/************************************************************************************************/
/* Core memory management running and CPU 0 fully initialized, try to keep code under this line */
/************************************************************************************************/
	dprintf("Loading pit driver...");
	/* We need to set up PIT driver so we can enable interrupts */
	pit_initialize();
	dprintf("OK\nCreating process 0...");
	/* Set up kernel process */
	setup_initial_process();
	dprintf("OK\nLoading IFS system...")	;
	/* Set up driver systems */
	ifs_initialize();
	dprintf("OK\nLoading devfs driver...");
	devfs_main(); 
	dprintf("OK\nLoading romfs driver...");
	romfs_main(); 
	dprintf("OK\nLoading pty driver...");
	pty_main();
	dprintf("OK\nLoading procfile driver...");
	procfile_main();
	dprintf("OK\nSetting up stdio streams...");

	/* Set up STDIO */
	pty_create_stdio(0);//For now, set initial stdio streams to pty0
	dprintf("OK\n");

	/* Load INITRD */
	initialize_initrd();


	/* Load INIT */
	dprintf("Executing /sbin/init.exe...\n");
	int retval = exec_pe("/sbin/init.exe");
	if (retval != 0 )
		dprintf("Retval %i %x\n",retval,retval);
	dprintf("PANIC: INIT exited\n");

dieloop:
	goto dieloop;
}